from common.settings import Settings
from common.utils.NovishLogger import logger
import json
import os
import pathlib
import sqlite3
import subprocess
import sys
import time
from functools import wraps
from itertools import chain

import pandas as pd
import psutil
import numpy as np

from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QMenu, QAction
from PyQt5.Qt import QPixmap, QImage, QIcon


def get_lane_scope(data, addition=10):
    """ 根据绘制的区域，获取车道范围
    Return:
        xmin, ymin, xmax, ymax
    """
    box = [item['points'] for item in data]
    box = np.array(list(chain.from_iterable(box)))
    x_sorted, y_sorted = np.sort(box[:, 0]), np.sort(box[:, 1])
    xmin, xmax = x_sorted[0], x_sorted[-1]
    ymin, ymax = y_sorted[0], y_sorted[-1]
    # 扩大区域
    xmin = max(0, xmin - addition)
    ymin = max(0, ymin - addition)
    xmax = min(5000, xmax + addition)
    ymax = min(5000, ymax + addition)
    return xmin, ymin, xmax, ymax


def overrides(func):
    @wraps(func)
    def inner(*args, **kwargs):
        """Overridden"""
        return func(*args, **kwargs)

    return inner


def read_conf(path, flag_pickled=True):
    """
    读取配置文件
    @param path:
    @param logger:
    @return:
    """
    try:
        if flag_pickled:
            return pd.read_pickle(path)
        else:
            return json.load(open(path, 'w'))
    except FileNotFoundError:
        logger.error(f'路径{path}不存在！')
        return {}
    except Exception as e:
        logger.error(f'Error[read_conf]: {e} [{path}]')
        return {}


def write_conf(data, path, flag_pickled=True):
    """
    写入当前数据到<path>指定的文件

    @param data:
            字典类型数据
    @param path:
            配置文件路径
    @param logger:
    @param flag_pickled:
    @return:
    """
    raw = read_conf(path, flag_pickled)
    raw.update(data)
    try:
        if flag_pickled:
            pd.to_pickle(raw, path)
        else:
            json.dump(raw, open(path, 'w'))
        return raw
    except Exception as e:
        logger.error(f'Error[write_conf]: {e} [{path}]')
        return {}


def save_default_conf(path):
    """
    Save default config to path
    @param path:

    @return:
    """
    data = read_conf(path)
    data = json.dumps(data, indent=4, ensure_ascii=False)
    pathlib.Path('ui/default_settings.py').write_text(data)


@logger.catch(reraise=True)
def kill_proc_tree(pid, including_parent=True):
    logger.info(f"kill process:{pid}")
    parent = psutil.Process(pid)
    children = parent.children(recursive=True)
    for child in children:
        try:
            child.kill()
        except Exception as e:
            logger.exception(e)

    _, _ = psutil.wait_procs(children, timeout=5)
    if including_parent:
        parent.kill()
        parent.wait(1)


@logger.catch(reraise=True)
def restart_app(pid, base_path):
    restart_app_linux(pid, base_path)
    # # 根据你的操作系统选择合适的函数
    # if os.name == 'posix':
    #     restart_app_linux(pid, base_path)
    # elif os.name == 'nt':
    #     restart_app_windows()


def find_process_by_name(process_name):
    """查找指定名称的进程"""
    matching_processes = []
    for proc in psutil.process_iter(['pid', 'name']):
        logger.info(f"Process {proc.pid}: {proc.info['name']}")
        if process_name.lower() in proc.info['name'].lower():
            matching_processes.append(proc)
    return matching_processes


def restart_app_windows():
    try:
        python = sys.executable
        logger.info('Restarting app on Windows...')

        # 查找进程
        matching_processes = find_process_by_name("python.exe")

        if not matching_processes:
            logger.warning("No processes found with name 'main.py'")
            return

        # 终止所有找到的进程
        for process in matching_processes:
            try:
                logger.debug(f"terminating Process {process.pid}")
                process.terminate()
            except psutil.NoSuchProcess:
                logger.warning(f"Process {process.pid} already terminated.")
            except Exception as e:
                logger.error(f"Failed to terminate process {process.pid}: {e}")

        # 等待一段时间让进程彻底终止
        gone, alive = psutil.wait_procs(matching_processes, timeout=3)

        # 如果有进程没有终止，则强制终止
        for process in alive:
            try:
                process.kill()
            except psutil.NoSuchProcess:
                logger.warning(f"Process {process.pid} already terminated.")
            except Exception as e:
                logger.error(f"Failed to kill process {process.pid}: {e}")

        # 启动新的进程
        python_executable = 'python'  # 或者使用sys.executable获取当前Python解释器路径
        subprocess.Popen([python_executable, "main.py"])
        # os.execl(python, python, *sys.argv)

    except subprocess.CalledProcessError as e:
        logger.error("Failed to terminate the process: %s", e)
    except FileNotFoundError:
        logger.error("Python executable not found.")
    except Exception as e:
        logger.error("An unexpected error occurred: %s", e)
        logger.exception(e)


@logger.catch(reraise=True)
def restart_app_linux(pid, base_path):
    """ 干掉子进程 重启主进程 """
    time.sleep(1)
    kill_proc_tree(pid, False)
    name = os.path.split(sys.argv[0])[-1]
    path = os.path.join(base_path, name)

    if '__compiled__' in globals():  # compiled with nuitka
        logger.info(f"restart_app os.execlp:{path} {name}")
        os.execlp(path, name)
    else:
        logger.info(f"restart_app python3 {path}")
        os.execl(sys.executable, 'python', path)


def explorer(f):
    """ 打开/定位 文件 """
    if f is None:
        logger.error('Error: path is None')
        return
    if not os.path.exists(f):
        logger.error(f'Error: 路径不存在: {f}')
        return
    try:
        os.system(f'explorer /select, "{f}')
        os.startfile(f)
    except Exception as e:
        logger.exception(e)
        subprocess.Popen(['xdg-open', f])


def push_data_to_db(data, table_name='RESULT_DATA', path=''):
    """
    Save data to DB
    @param data:
    @param table_name: table name
    @param path: db filepath
    @return:
    """
    conn = sqlite3.connect(str(path))

    # 等长
    max_len = max([len(v) for v in data.values()])
    for k, v in data.items():
        data[k].extend(['', ] * (max_len - len(v)))

    df = pd.DataFrame(data)
    df.to_sql(name=table_name, con=conn, if_exists='append', index=False)

    conn.close()


def add_actions(widget, actions):
    for action in actions:
        if action is None:
            widget.addSeparator()
        elif isinstance(action, QMenu):
            widget.addMenu(action)
        else:
            widget.addAction(action)


def new_action(parent, text, slot=None, shortcut=None, icon=None,
               tip=None, checkable=False, enabled=True):
    """Create a new action and assign callbacks, shortcuts, etc."""
    a = QAction(text, parent)
    if icon is not None:
        a.setIcon(new_icon(icon))
    if shortcut is not None:
        if isinstance(shortcut, (list, tuple)):
            a.setShortcuts(shortcut)
        else:
            a.setShortcut(shortcut)
    if tip is not None:
        a.setToolTip(tip)
        a.setStatusTip(tip)
    if slot is not None:
        a.triggered.connect(slot)
    if checkable:
        a.setCheckable(True)
    a.setEnabled(enabled)
    return a


def new_icon(icon):
    return QIcon(':/' + icon)


def get_qicon(byte_str):
    """
    Get QIcon
    @param byte_str:
        byte string
    @return:
    """
    return QIcon(QPixmap(QImage.fromData(byte_str)))


def new_action(parent, text, slot=None, shortcut=None, icon=None,
               tip=None, checkable=False, enabled=True):
    """Create a new action and assign callbacks, shortcuts, etc."""
    a = QAction(text, parent)
    if icon is not None:
        a.setIcon(new_icon(icon))
    if shortcut is not None:
        if isinstance(shortcut, (list, tuple)):
            a.setShortcuts(shortcut)
        else:
            a.setShortcut(shortcut)
    if tip is not None:
        a.setToolTip(tip)
        a.setStatusTip(tip)
    if slot is not None:
        a.triggered.connect(slot)
    if checkable:
        a.setCheckable(True)
    a.setEnabled(enabled)
    return a


def add_actions(widget, actions):
    for action in actions:
        if action is None:
            widget.addSeparator()
        elif isinstance(action, QMenu):
            widget.addMenu(action)
        else:
            widget.addAction(action)
